package rothag.models;

import rothag.models.ressources.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Random;
import java.util.UUID;
import javax.swing.event.EventListenerList;
import rothag.models.desastres.*;
import rothag.models.developpement.*;
import rothag.enums.*;
import rothag.listener.*;
import rothag.models.monuments.GrandeMuraille;

/**
 * Classe d'implémentation des régles du jeu
 * @author gulian.lorini, Gaëtan
 * @version 0.2
 */
public class GameManager {

    private Partie partie;
    private EventListenerList listeners;
    private int nbLances;

    /**
     * Constructeur
     */
    public GameManager() {
        // Initialisation de la liste des écouteurs
        listeners = new EventListenerList();
    }

    /**
     * Permet de crée une nouvelle partie et de la lancer
     * @param listeNom Liste des noms des joueurs
     */
    public void nouvellePartie(ArrayList<String> listeNom) {

        try {
            // Création de la nouvelle partie
            partie = new Partie(listeNom);
        } catch (Exception ex) {
            System.err.println("Erreur lors de la création de la nouvelle partie.");
            System.exit(1);
        }

        // Lancement de la partie
        lancerPartie();
    }

    /**
     * Lance la partie
     */
    public void lancerPartie() {

        if (partie == null) {
            // La partie n'est pas valide
            System.err.println("Erreur lors du lancement de la partie.");
            System.exit(1);
        } else {
            if (partie.getJoueurCourant() == null) {
                // Premier lancement
                partie.setJoueurCourant(partie.getListeJoueur().get(0));
            }

            if (partie.getEtapeCourante() == null || partie.getEtapeCourante() == EtapeJeu.AUCUNE) {
                // Premier lancement
                partie.setEtapeCourante(EtapeJeu.LANCERDES);
            }

            // Mise à jour des écouteurs avec les élements de la partie
            fireListeJoueurChanged(partie.getListeJoueur());
            fireJoueurCourantChanged(partie.getJoueurCourant());
            fireTourChanged(partie.getTour());
            fireEtapeJeuChanged(partie.getEtapeCourante());
            firePartieStarted();
        }
    }

    /**
     * Retourne la partie courante
     * @return Une partie
     */
    public Partie getPartie() {
        return partie;
    }

    /**
     * Permet de charger une partie à partie d'un identifiant
     * @param identifiant Identifiant de la partie
     * @throws Exception Fichier de sauvegarde invalide
     */
    public void chargerPartie(UUID identifiant) throws Exception {
        // Récupération de la partie
        partie = Serialisation.getSauvegardes().get(identifiant);
        // Lancement
        lancerPartie();
    }

    /**
     * Enregistre la partie courante
     * @throws Exception Fichier de souvegarde invalide
     */
    public void enregistrerPartie() throws Exception {
        Serialisation.sauvegarderPartie(partie);
    }

    /**
     * Ajoute la partie au TOP 10
     * @return Classement dans le TOP 10, -1 si non classé
     * @throws Exception Fichier de sauvegarde non valide
     */
    public int ajouterScore() throws Exception {
        return Serialisation.ajouterScore(partie);
    }

    /**
     * Retourne la liste du TOP 10
     * @return Liste de partie
     * @throws Exception Fichier de sauvegarde invalide
     */
    public ArrayList<PartieInterface> getTopTen() throws Exception {
        ArrayList<PartieInterface> liste = new ArrayList<PartieInterface>(Serialisation.getTopTen());
        return liste;
    }

    /**
     * Met à jour l'affiche pour le lancement des dés
     */
    public void lancerDes() {
        Joueur joueur = (Joueur) partie.getJoueurCourant();
        // RAZ des dés du joueur
        joueur.setListeDes(new ArrayList<TypeDes>());
        // RAZ du nombre de pièces
        joueur.setPiece(0);

        // Gestion du développement Conservation
        if (joueur.getNourriture() < 15 && joueur.getNourriture() > 0 && joueur.estRealiseDeveloppement(partie.chercheDeveloppement(Conservation.class))) {
            joueur.getPoterie().deleteOneRessource();
            joueur.setNourriture(joueur.getNourriture() * 2);
            if (joueur.getNourriture() > 15) {
                joueur.setNourriture(15);
            }
            // Mise à jour des écouteurs pour poterie et nourriture
            fireNourritureChanged(joueur.getNourriture());
            firePoterieChanged(joueur.getPoterie().getQuantite());
        }
        nbLances = 0;

        // Mise à jour des écouteurs
        firePieceChanged(joueur.getPiece());
        fireDesGardesChanged();
    }

    /**
     * Génère les dés nécessaire pour la sélection par le joueur
     * @return Une liste de dés alétoire
     */
    public ArrayList<TypeDes> genereDes() {
        Joueur joueur = (Joueur) partie.getJoueurCourant();
        ArrayList<TypeDes> listeDes = new ArrayList<TypeDes>();

        Random rand = new Random();
        int nbVilles = joueur.getNbVilles();
        int nbJoueur = partie.getListeJoueur().size();
        int nbMax = nbVilles - joueur.getListeDes().size();

        nbLances++;

        // Génération aléatoire des dés
        for (int i = 0; i < nbMax; i++) {
            int random = Math.abs(rand.nextInt()) % 6;

            TypeDes deTire = null;
            switch (random) {
                case 0:
                    deTire = TypeDes.CRANE;
                    break;
                case 1:
                    deTire = TypeDes.JARRE;
                    break;
                case 2:
                    deTire = TypeDes.NOURRITURE;
                    break;
                case 3:
                    deTire = TypeDes.PIECE;
                    break;
                case 4:
                    deTire = TypeDes.TRAVAILLEURS;
                    break;
                case 5:
                    deTire = TypeDes.TRAVAILLEURSNOURRITURE;
                    break;
            }

            if (nbJoueur > 1 && deTire == TypeDes.CRANE || nbLances >= joueur.getNbLancesAurorises()) {
                // Si le dés est un crâne ou que le joueur n'a plus de lancé possible, 
                // on ajoute le dé à sa liste de dés sélectionnée
                joueur.getListeDes().add(deTire);
            } else {
                // Ajout du dé à la liste de retour
                listeDes.add(deTire);
            }
        }

        fireDesGardesChanged();

        return listeDes;
    }

    /**
     * Ajoute le dé au joueur
     * @param des Dé à ajouter
     */
    public void selectionnerDes(TypeDes des) {
        Joueur joueur = (Joueur) partie.getJoueurCourant();
        joueur.getListeDes().add(des);

        // Mise à jour des écouteurs
        fireDesGardesChanged();
    }

    /**
     * Retire un dé de la liste des dés du joueur
     * @param des Dé à retirer
     * @return Vrai si l'opération est réussite
     */
    public boolean deselectionnerDes(TypeDes des) {
        boolean retour = false;

        Joueur joueur = (Joueur) partie.getJoueurCourant();

        if (nbLances == joueur.getNbLancesAurorises()) {
            // Le joueur n'a plus de lancés
            if (joueur.estRealiseDeveloppement(partie.chercheDeveloppement(Conduite.class)) && joueur.getNbVilles() == joueur.getListeDes().size()) {
                // S'il a le développement conduite il peut relancer un dés
                joueur.getListeDes().remove(des);
                retour = true;

                // Mise à jour des écouteurs
                fireDesGardesChanged();
            }
        } else if (nbLances < joueur.getNbLancesAurorises()) {
            // Il reste encore des lancés au joueur
            if (des != TypeDes.CRANE || partie.getListeJoueur().size() == 1) {
                joueur.getListeDes().remove(des);
                retour = true;

                // Mise à jour des écouteurs
                fireDesGardesChanged();
            }
        }
        return retour;
    }

    /**
     * Met à jour les ressources du joueurs à partir des dés
     */
    public void collecterRessources() {
        Joueur joueur = (Joueur) partie.getJoueurCourant();

        // Récupération des valeurs actuelles
        int nbPiece = joueur.getPiece();
        int nbNourriture = joueur.getNourriture();
        int nbOuvriers = joueur.getOuvrier();
        int nbRessource = 0;
        int nbCranes = 0;

        // Parcours des dés obtenus
        for (TypeDes de : joueur.getListeDes()) {
            if (de == TypeDes.CRANE) {
                nbCranes++;
                nbRessource += 2;
            } else if (de == TypeDes.JARRE) {
                nbRessource++;
            } else if (de == TypeDes.NOURRITURE) {
                nbNourriture += 3;
                if (joueur.estRealiseDeveloppement(partie.chercheDeveloppement(Agriculture.class))) {
                    // Nourriture supplémentaire grâce au développement Agriculture
                    nbNourriture++;
                }
            } else if (de == TypeDes.PIECE) {
                nbPiece += 7;
                if (joueur.estRealiseDeveloppement(partie.chercheDeveloppement(Finance.class))) {
                    // 5 pièces supplémentaires avec le développement Finance
                    nbPiece += 5;
                }
            } else if (de == TypeDes.TRAVAILLEURS) {
                nbOuvriers += 3;
                if (joueur.estRealiseDeveloppement(partie.chercheDeveloppement(Maconnerie.class))) {
                    // Ouvrier supplémentaire grâce au développement Maconnerie
                    nbOuvriers++;
                }
            }
        }

        // Mise à jour du joueur
        joueur.setPiece(nbPiece);
        joueur.setNourriture(nbNourriture);
        joueur.setOuvrier(nbOuvriers);
        joueur.setCrane(nbCranes);

        int indice = 0;
        int pas = 1;

        // Récupération des ressources du joueur
        Ressource[] tabRessources = new Ressource[5];
        tabRessources[0] = joueur.getBois();
        tabRessources[1] = joueur.getPierre();
        tabRessources[2] = joueur.getPoterie();
        tabRessources[3] = joueur.getTissu();
        tabRessources[4] = joueur.getFer();

        int saveNbPierre = joueur.getPierre().getQuantite();

        // Ajout des ressources
        while (nbRessource >= 1) {
            // Ajout d'une ressource
            tabRessources[indice].addOneRessource();
            // Avancement dans l'indice
            indice += pas;

            if (pas != 0) {
                if (indice == 4 || indice == 0) {
                    pas = 0;
                }
            } else {
                if (indice == 4) {
                    pas = -1;
                } else if (indice == 0) {
                    pas = 1;
                }
            }

            nbRessource--;
        }

        if (joueur.estRealiseDeveloppement(partie.chercheDeveloppement(Carriere.class))) {
            if (saveNbPierre < joueur.getPierre().getQuantite()) {
                // Ajout du pierre grâce au développement Carriere
                joueur.getPierre().addOneRessource();
            }
        }

        // Mise à jour des écouteurs
        firePieceChanged(nbPiece);
        fireNourritureChanged(nbNourriture);
        fireOuvrierChanged(nbOuvriers);
        fireBoisChanged(joueur.getBois().getQuantite());
        firePierreChanged(joueur.getPierre().getQuantite());
        firePoterieChanged(joueur.getPoterie().getQuantite());
        fireTissuChanged(joueur.getTissu().getQuantite());
        fireFerDeLanceChanged(joueur.getFer().getQuantite());
    }

    /**
     * Permet de passer à l'étape de sélection des dés (Ouvriers/Nourritures) ou bien directement à l'étape nourrir
     */
    public void collecterNourritureOuvriers() {
        Joueur joueur = (Joueur) partie.getJoueurCourant();

        int nbChoix = 0;
        for (TypeDes de : joueur.getListeDes()) {
            if (de == TypeDes.TRAVAILLEURSNOURRITURE) {
                nbChoix++;
            }
        }

        if (nbChoix > 0) {
            // Passage en mode sélection des dés
            partie.setEtapeCourante(EtapeJeu.NOURRIR_SELECTION);
        } else {
            // On nourrit les villes, résout les désastres et affiche le résultat
            nourrirVilles(0, 0);
            resoudreDesastres();
            partie.setEtapeCourante(EtapeJeu.NOURRIR_INFOS);
        }
    }

    /**
     * Récupère les dés sélectionnés et nourrit les villes
     */
    public void nourrirVilles(int nbOuvriersSel, int nbNourritureSel) {
        Joueur joueur = (Joueur) partie.getJoueurCourant();

        // Mise à jour des infos du joueur après la sélection des dés
        joueur.setOuvrier(joueur.getOuvrier() + nbOuvriersSel);
        joueur.setNourriture(joueur.getNourriture() + nbNourritureSel);

        // Alimentation des villes
        int nourriture = joueur.getNourriture();
        nourriture -= joueur.getNbVilles();

        if (nourriture < 0) {
            joueur.setNourriture(0);

            // Mise à jour du score
            int score = joueur.getScore() + nourriture;
            joueur.setScore(score);

            nourriture = Math.abs(nourriture);

            // Mise à jour des écouteurs
            fireScoreChanged(score);
            fireFamineSufferedChanged(nourriture);

            // Logs
            StringBuilder text = new StringBuilder("<br>    <b><font color='#FF0000'>");
            if (nourriture > 1) {
                text.append(nourriture);
                text.append(" villes ont subi la famine");
            } else {
                text.append("1 ville a subi la famine");
            }
            text.append("</font></b>");
            logAction(text.toString(), true);
        } else {
            joueur.setNourriture(nourriture);
            fireFamineSufferedChanged(0);

            // Logs
            logAction("<br>    Pas de famine", true);
        }

        // Mise à jour des écouteurs
        fireNourritureChanged(joueur.getNourriture());
        fireOuvrierChanged(joueur.getOuvrier());
    }

    /**
     * Fait subir au joueur les désastres
     */
    public void resoudreDesastres() {
        Joueur joueur = (Joueur) partie.getJoueurCourant();
        ArrayList<JoueurInterface> listeJoueur = partie.getListeJoueur();

        int i, points = 0;

        // Désastres
        Desastre desastre = null;
        switch (joueur.getCrane()) {
            case 0:
            case 1:
                break;
            case 2:
                if (!joueur.estRealiseDeveloppement(partie.chercheDeveloppement(Irrigation.class))) {
                    // 2 crânes et pas de développement Irrigation
                    desastre = new Secheresse();
                    joueur.setScore(joueur.getScore() - 2);
                }
                break;
            case 3:
                if (!joueur.estRealiseDeveloppement(partie.chercheDeveloppement(Medecine.class))) {
                    // Si le joueur n'a pas le développement médecine il est affecté
                    desastre = new Peste();
                }
                // Tous les joueurs qui n'ont pas la médecine sont aussi affectés
                for (JoueurInterface jInterface : partie.getListeJoueur()) {
                    Joueur j = (Joueur) jInterface;
                    if (!j.estRealiseDeveloppement(partie.chercheDeveloppement(Medecine.class))) {
                        j.setScore(j.getScore() - 3);
                    }
                }
                break;
            case 4:
                MonumentJoueurInterface monument = joueur.getMonumentJoueur(GrandeMuraille.class);
                if ((monument == null || !monument.getRealise()) && !joueur.estRealiseDeveloppement(partie.chercheDeveloppement(Forge.class))) {
                    // Si le joueur n'a pas la grande muraille ou le développement Forge
                    desastre = new Invasion();
                    joueur.setScore(joueur.getScore() - 4);
                } else if (joueur.estRealiseDeveloppement(partie.chercheDeveloppement(Forge.class))) {
                    // Si le joueur à le développement Forge, l'invasion se retourne contre les autres
                    points = 4 + 2 * joueur.getFer().getIndice();
                    joueur.getFer().deleteAllRessources();

                    for (i = 0; i < listeJoueur.size(); i++) {
                        Joueur j = (Joueur) listeJoueur.get(i);
                        if (j != joueur && !j.estRealiseDeveloppement(partie.chercheDeveloppement(GrandeMuraille.class))) {
                            j.setScore(j.getScore() - points);
                        }
                    }

                    // Mise à jour des écouteurs
                    fireFerDeLanceChanged(joueur.getFer().getQuantite());
                }

                break;
            default:
                if (joueur.estRealiseDeveloppement(partie.chercheDeveloppement(Religion.class))) {
                    // Si le joueur à le développement Religion, tous les autres perdent leurs ressources
                    for (JoueurInterface jInterface : partie.getListeJoueur()) {
                        if (jInterface != joueur) {
                            Joueur j = (Joueur) jInterface;
                            j.getBois().deleteAllRessources();
                            j.getFer().deleteAllRessources();
                            j.getPierre().deleteAllRessources();
                            j.getTissu().deleteAllRessources();
                            j.getPoterie().deleteAllRessources();
                        }
                    }
                } else {
                    // Sinon le joueur perd toutes ses ressrouces
                    desastre = new Revolte();
                    joueur.getBois().deleteAllRessources();
                    joueur.getFer().deleteAllRessources();
                    joueur.getPierre().deleteAllRessources();
                    joueur.getTissu().deleteAllRessources();
                    joueur.getPoterie().deleteAllRessources();

                    // Mise à jour des écouteurs
                    fireBoisChanged(joueur.getBois().getQuantite());
                    fireFerDeLanceChanged(joueur.getFer().getQuantite());
                    firePierreChanged(joueur.getPierre().getQuantite());
                    fireTissuChanged(joueur.getTissu().getQuantite());
                    firePoterieChanged(joueur.getPoterie().getQuantite());
                }
                break;
        }

        // Logs
        StringBuilder text = new StringBuilder("<br> Nombre de crânes : ");
        text.append(joueur.getCrane());
        if (desastre != null) {
            text.append(", <b><font color='#FF0000'>");
            text.append(desastre.getClass().getSimpleName());
            text.append("</font></b>");
        } else {
            text.append(" (pas d'impact)");
        }
        logAction(text.toString(), true);

        // Mise à jour des écouteurs
        fireScoreChanged(joueur.getScore());
        fireDesastreSufferedChanged(desastre);
    }

    /**
     * Permet au joueur courant de vendre une pierre contre trois ouvriers
     */
    public void vendrePierrePourOuvrier() {

        Joueur joueur = (Joueur) partie.getJoueurCourant();

        if (joueur.estRealiseDeveloppement(partie.chercheDeveloppement(Ingenierie.class))) {
            if (joueur.getPierre().getQuantite() > 0) {
                joueur.getPierre().deleteOneRessource();
                joueur.setOuvrier(joueur.getOuvrier() + 3);

                // Mise à jour des écouteurs
                fireOuvrierChanged(joueur.getOuvrier());
                firePierreChanged(joueur.getPierre().getQuantite());
            }
        }
    }

    /**
     * Permet de rajouter un ouvrier à une ville
     * @param v Ville en question
     * @return Vrai si la ressource ouvrière était suffisante
     */
    public boolean construireVille(VilleInterface v) {
        boolean constVille = false;
        Joueur joueur = (Joueur) partie.getJoueurCourant();
        Ville ville = (Ville) v;

        if (!ville.getRealise()) {
            if (joueur.getOuvrier() > 0) {
                // Mise à jour du nombre d'ouvriers
                joueur.setOuvrier(joueur.getOuvrier() - 1);
                // Avancement de la construction
                ville.setNombreOuvrierSelectionne(ville.getNombreOuvrierSelectionne() + 1);

                if (ville.getNombreOuvrierNecessaire() == ville.getNombreOuvrierSelectionne()) {
                    // Ville construire
                    ville.setRealise(true);
                    joueur.setNbVilleConstruite(joueur.getNbVilleConstruite() + 1);

                    // Prise en compte du développement Empire
                    if (joueur.estRealiseDeveloppement(partie.chercheDeveloppement(Empire.class))) {
                        // 1 points supplémentaire par ville
                        joueur.setScore(joueur.getScore() + 1);
                        fireScoreChanged(joueur.getScore());
                    }
                }
                constVille = true;

                fireOuvrierChanged(joueur.getOuvrier());
            }
        }

        return constVille;
    }

    /**
     * Permet de rajouter un ouvrier à un monument
     * @param m Monument en question
     * @return Vrai si la ressource ouvrière était suffisante
     */
    public boolean construireMonument(MonumentJoueurInterface m) {
        boolean mon_realise = false;
        int point_mon = 0;
        MonumentJoueur monumJoueur = (MonumentJoueur) m;

        if (!monumJoueur.getRealise()) {
            Joueur joueur = monumJoueur.getJoueur();
            if (joueur.getOuvrier() > 0) {
                // Mise à jour du nombre d'ouvriers
                joueur.setOuvrier(joueur.getOuvrier() - 1);
                // Avancement de la construction du monuement
                monumJoueur.setNombreOuvriersSelectionnes(monumJoueur.getNombreOuvriersSelectionnes() + 1);

                if (monumJoueur.getMonument().getNbOuvrierNecessaire() == monumJoueur.getNombreOuvriersSelectionnes()) {
                    // Monument construit
                    monumJoueur.setRealise(true);
                    if (monumJoueur.getMonument().getRealise()) {
                        // Si le monument a déjà été construit, points min
                        point_mon = monumJoueur.getMonument().getPointsMin();
                    } else {
                        // Si le monument n'a pas déjà été construit, points max et mise à jour du monument
                        point_mon = monumJoueur.getMonument().getPointsMax();
                        monumJoueur.getMonument().setRealise(true);
                    }
                    // Mise jour du joueur
                    joueur.setScore(joueur.getScore() + point_mon);
                    joueur.setNbMonumentConstruit(joueur.getNbMonumentConstruit() + 1);

                    // Prise en compte du développement Architecture
                    if (joueur.estRealiseDeveloppement(partie.chercheDeveloppement(Architecture.class))) {
                        // Deux points supplémentaires
                        joueur.setScore(joueur.getScore() + 2);
                    }

                    fireScoreChanged(joueur.getScore());
                }
                mon_realise = true;

                fireOuvrierChanged(joueur.getOuvrier());
            }
        }
        return mon_realise;
    }

    /**
     * Permet de constuire un bateau
     * @return Vrai les ressource le permettent
     */
    public boolean construireBateau() {
        boolean constBat = false;
        Joueur joueur = (Joueur) partie.getJoueurCourant();

        if (joueur.estRealiseDeveloppement(partie.chercheDeveloppement(Navigation.class))) {
            // Le joueur possède le développement navigation
            if (joueur.getBois().getQuantite() > 0 && joueur.getTissu().getQuantite() > 0) {
                // Le joueur possède les ressources nécessaires
                // Mise à jour du joueur
                joueur.getBois().deleteOneRessource();
                joueur.getTissu().deleteOneRessource();
                joueur.setBateau(joueur.getBateau() + 1);
                joueur.setNbBateauConstruit(joueur.getNbBateauConstruit() + 1);

                constBat = true;

                // Mise à jour des écouteurs
                fireBateauChanged(joueur.getBateau());
                fireBoisChanged(joueur.getBois().getQuantite());
                fireTissuChanged(joueur.getTissu().getQuantite());
            }
        }
        return constBat;
    }

    /**
     * Met à jour la ressource du joueur courant
     * @param ressource Ressource à mettre à jour
     * @param nbRessource Valeur à ajouter à la ressource (négatif pour soustraire)
     */
    public void majRessourceJoueurCourant(Class ressource, int nbRessource, boolean log) {
        Joueur joueur = (Joueur) partie.getJoueurCourant();

        // Récupération de la ressource à mettre à jour
        Ressource res = joueur.getRessource(ressource);
        if (res != null && nbRessource != 0) {
            StringBuilder text = new StringBuilder();
            if (nbRessource > 0) {
                // Ajout
                res.addRessource(nbRessource);
                // Logs
                text.append("Achat de ");
            } else {
                // Suppression
                res.deleteRessource(Math.abs(nbRessource));
                // Logs
                text.append("Vente de ");
            }

            // Logs
            text.append(Math.abs(nbRessource));
            text.append(" ");
            text.append(res.toString());
            if (log) {
                logAction(text.toString(), true);
            }

            // Mise à jour des écouteurs
            fireRessourceChanged(ressource, res.getQuantite());
        }
    }

    /**
     * Permet de vendre une ressource contre des pièces
     * @param ressource
     */
    public void vendreRessource(Class ressource) {
        Joueur joueur = (Joueur) partie.getJoueurCourant();
        // Récupération de la ressource
        Ressource res = joueur.getRessource(ressource);
        if (res != null) {
            // Mise à jour de la ressource et des pièces
            int nbRessource = res.getQuantite();
            res.deleteAllRessources();
            joueur.setPiece(joueur.getPiece() + nbRessource);

            // Mise à jour des écouteurs
            firePieceChanged(joueur.getPiece());
            fireRessourceChanged(ressource, res.getQuantite());
        }
    }

    /**
     * Retourne la liste des développements possibles
     * @return Liste de développements
     */
    public ArrayList<Developpement> getlisteDeveloppementPossible() {
        return partie.getlisteDeveloppementPossible();
    }

    /**
     * Ajoute un développement au joueur courant
     * @param dev Développement à réaliser
     * @param listRessource Ressources à utiliser
     * @param nbNourriture Nourriture à utiliser
     * @return Vrai si le développement a été réalisé
     */
    public boolean ajouterDeveloppementRealise(Developpement dev, ArrayList<Class> listRessource, int nbNourriture) {
        Joueur joueur = (Joueur) partie.getJoueurCourant();
        int totalRessource = 0;

        ArrayList<Ressource> list = new ArrayList<Ressource>();
        if (listRessource != null) {
            for (Class c : listRessource) {
                list.add(joueur.getRessource(c));
            }
        }

        // Reccuperation des quantités de ressource échangée
        if (list != null) {
            for (Ressource r : list) {
                totalRessource += r.getQuantite();
            }
        }

        // Vérification de la nourriture
        if (nbNourriture > joueur.getNourriture()) {
            nbNourriture = joueur.getNourriture();
        }

        // Prise en compte du développement Grenier
        if (!joueur.estRealiseDeveloppement(partie.chercheDeveloppement(Grenier.class))) {
            nbNourriture = 0;
        }

        if (totalRessource + nbNourriture * 6 + joueur.getPiece() >= dev.getCout()) {
            // Le développement peut être réalisé

            if (totalRessource + nbNourriture * 6 < dev.getCout()) {
                // Le joueur doit utiliser ses pièces pour acheter le développement
                joueur.setPiece(joueur.getPiece() - (dev.getCout() - totalRessource - nbNourriture * 6));
                firePieceChanged(joueur.getPiece());
            }

            // Mise à jour des ressources
            if (list != null) {
                for (Ressource r : list) {
                    r.deleteAllRessources();
                    fireRessourceChanged(r.getClass(), r.getQuantite());
                }
            }

            // Mise à jour du joueur
            joueur.setNourriture(joueur.getNourriture() - nbNourriture);
            joueur.setDeveloppementRealise(dev);
            joueur.setScore(joueur.getScore() + dev.getPoints());

            // Prise en compte du développement Empire
            if (dev.getClass() == Empire.class) {
                joueur.setScore(joueur.getScore() + joueur.getNbVilles());
            }

            // Logs
            StringBuilder text = new StringBuilder("<br> Développement réalisé : ");
            text.append(dev.getNom());
            logAction(text.toString(), true);

            // Mise à jour des écouteurs
            fireNourritureChanged(joueur.getNourriture());
            fireScoreChanged(joueur.getScore());
            fireListeDeveloppementChanged(joueur.getListeDeveloppements());

            return true;
        } else {
            return false;
        }
    }

    /**
     * Retourne le nombre de ressources à defausser par le joueur
     */
    public int defausse() {
        Joueur joueur = (Joueur) partie.getJoueurCourant();

        boolean devCaravane = joueur.estRealiseDeveloppement(partie.chercheDeveloppement(Caravanes.class));
        int nbDefausse;

        if (!devCaravane) {
            nbDefausse = joueur.getNbTotalRessources() - 6;
            if (nbDefausse >= 0) {
                return nbDefausse;
            } else {
                return 0;
            }
        } else {
            return 0;
        }
    }

    /**
     * Retourne le nombre de chaque ressource du joueur
     * @return Dictionnaire de la forme : Ressource/Nombre
     */
    public HashMap<Class, Integer> getNombreRessourceJoueurCourant() {
        Joueur joueur = (Joueur) partie.getJoueurCourant();

        if (joueur != null) {
            // Création du dictionnaire
            HashMap<Class, Integer> retour = new HashMap<Class, Integer>();
            retour.put(Bois.class, joueur.getBois().getIndice());
            retour.put(Pierre.class, joueur.getPierre().getIndice());
            retour.put(Poterie.class, joueur.getPoterie().getIndice());
            retour.put(Tissu.class, joueur.getTissu().getIndice());
            retour.put(FerDeLance.class, joueur.getFer().getIndice());

            return retour;
        } else {
            return null;
        }
    }

    /**
     * Retourne le classement des joueurs
     * @return Liste de joueur classé
     */
    public ArrayList<JoueurInterface> getClassementJoueur() {
        ArrayList<JoueurInterface> classement = new ArrayList<JoueurInterface>();

        for (JoueurInterface j : partie.getListeJoueur()) {
            int i;
            for (i = 0; i < classement.size(); i++) {
                JoueurInterface tmp = classement.get(i);
                if (j.getScore() > tmp.getScore()) {
                    // Score plus élevé
                    break;
                } else if (j.getScore() == classement.get(i).getScore()) {
                    // Scores égaux
                    if (j.getSommeRessource() > tmp.getSommeRessource()) {
                        // Nombre de ressources plus élevé
                        break;
                    }
                }
            }
            classement.add(i, j);
        }

        return classement;
    }

    /**
     * Permet de passer à l'étape suivante
     */
    public void etapeSuivante() {

        switch (partie.getEtapeCourante()) {
            case LANCERDES:
                // Logs des dés
                logDesJoueurCourant();
                // Collecte des ressources
                collecterRessources();
                // Collecte de la nourriture et des ouvriers
                collecterNourritureOuvriers();
                break;
            case NOURRIR_SELECTION:
                partie.setEtapeCourante(EtapeJeu.NOURRIR_INFOS);
                break;
            case NOURRIR_INFOS:
                partie.setEtapeCourante(EtapeJeu.CONSTRUIRE);
                break;
            case CONSTRUIRE:
                // Logs constructions
                logConstructionsJoueurCourant();
                Joueur j = (Joueur) partie.getJoueurCourant();
                if (j.getBateau() > 0) {
                    // Au moins un bateau donc commerce
                    partie.setEtapeCourante(EtapeJeu.COMMERCE);
                } else {
                    // Pas de bateaux...pas de commerce !
                    partie.setEtapeCourante(EtapeJeu.DEVELOPPEMENT);
                }
                break;
            case COMMERCE:
                partie.setEtapeCourante(EtapeJeu.DEVELOPPEMENT);
                break;
            case DEVELOPPEMENT:
                partie.setEtapeCourante(EtapeJeu.DEFAUSSE);
                break;
            case DEFAUSSE:
                if (partie.estTermine()) {
                    // Prise compte du développement Commerce
                    for (JoueurInterface jInterface : partie.getListeJoueur()) {
                        j = (Joueur) jInterface;
                        if (j.estRealiseDeveloppement(partie.chercheDeveloppement(Commerce.class))) {
                            j.setScore(j.getScore() + j.getBois().getIndice());
                            j.setScore(j.getScore() + j.getPierre().getIndice());
                            j.setScore(j.getScore() + j.getTissu().getIndice());
                            j.setScore(j.getScore() + j.getPoterie().getIndice());
                            j.setScore(j.getScore() + j.getFer().getIndice());
                        }
                    }

                    partie.setEtapeCourante(EtapeJeu.FIN_PARTIE);
                } else {
                    partie.setEtapeCourante(EtapeJeu.LANCERDES);
                    Joueur joueur = (Joueur) partie.getJoueurCourant();

                    // Calcul du nouvel index du joueur
                    int index = (partie.getListeJoueur().indexOf(joueur) + 1) % partie.getListeJoueur().size();
                    if (index <= partie.getListeJoueur().indexOf(joueur)) {
                        partie.setTour(partie.getTour() + 1);
                    }
                    partie.setJoueurCourant(partie.getListeJoueur().get(index));
                    joueur = (Joueur) partie.getJoueurCourant();

                    partie.getListeActionsJoueurCourant().clear();

                    // Log nouveau tour
                    logAction("<br><b>===== Nouveau tour =====</b>", false);
                    // RAZ des constructions
                    joueur.setNbVilleConstruite(0);
                    joueur.setNbMonumentConstruit(0);
                    joueur.setNbBateauConstruit(0);

                    // Mise à jour des écouteurs
                    fireTourChanged(partie.getTour());
                    fireJoueurCourantChanged(joueur);
                }
                break;
            default:
                break;
        }

        // Mise à jour des écouteurs
        fireEtapeJeuChanged(partie.getEtapeCourante());
    }

    /**
     * Permet de logger l'action d'un joueur
     * @param action Action à logger
     * @param logPartie Ajout de l'action dans le log de la partie
     */
    public void logAction(String action, boolean logPartie) {
        Joueur joueur = (Joueur) partie.getJoueurCourant();
        joueur.getListeActions().add(action);

        if (logPartie) {
            partie.getListeActionsJoueurCourant().add(action);
        }
    }

    /**
     * Permet de logger les dés obtenus
     */
    private void logDesJoueurCourant() {
        StringBuilder text = new StringBuilder("<br> Dés obtenus : ");
        for (TypeDes de : partie.getJoueurCourant().getListeDes()) {
            text.append("<br> - ");
            text.append(de.toString());
        }
        logAction(text.toString(), false);
    }

    /**
     * Permet de logger les constructions du tour
     */
    private void logConstructionsJoueurCourant() {
        Joueur joueur = (Joueur) partie.getJoueurCourant();
        int nbVille = joueur.getNbVilleConstruite();
        int nbMonument = joueur.getNbMonumentConstruit();
        int nbBateau = joueur.getNbBateauConstruit();

        StringBuilder text = new StringBuilder("<br> Constructions effectuées :");
        text.append("<br> - ");
        text.append(nbVille);
        text.append(" ville");
        if (nbVille > 1) {
            text.append("s");
        }
        text.append("<br> - ");
        text.append(nbMonument);
        text.append(" monument");
        if (nbMonument > 1) {
            text.append("s");
        }
        text.append("<br> - ");
        text.append(nbBateau);
        text.append(" bateau");
        if (nbBateau > 1) {
            text.append("x");
        }

        logAction(text.toString(), true);
    }

    /**
     * Retourne la liste des parties sauvegardés
     * @return Liste de partie
     * @throws Exception Fichier de sauvegarde non valide
     */
    public ArrayList<PartieInterface> getPartiesSauvees() throws Exception {
        ArrayList<PartieInterface> liste = new ArrayList<PartieInterface>(Serialisation.getSauvegardes().values());
        return liste;
    }

    /**
     * Permet d'ajout un écouteur sur le joueur courant
     * @param listener Ecouteur
     */
    public void addJoueurCourantListener(JoueurCourantListener listener) {
        listeners.add(JoueurCourantListener.class, listener);
    }

    /**
     * Retourne les écouteur de joueur courant
     * @return Ecouteurs
     */
    public JoueurCourantListener[] getJoueurCourantListeners() {
        return listeners.getListeners(JoueurCourantListener.class);
    }

    /**
     * Notifie que le score du joueur courant a changé
     * @param score Score
     */
    protected void fireScoreChanged(int score) {
        for (JoueurCourantListener listener : getJoueurCourantListeners()) {
            listener.scoreChanged(score);
        }
    }

    /**
     * Notifie que le nombre de bateaux du joueur courant a changé
     * @param nbBateau Nombre de bateaux
     */
    protected void fireBateauChanged(int nbBateau) {
        for (JoueurCourantListener listener : getJoueurCourantListeners()) {
            listener.bateauChanged(nbBateau);
        }
    }

    /**
     * Notifie que le nombre de pièces du joueur courant a changé
     * @param nbPiece Nombre de pièces
     */
    protected void firePieceChanged(int nbPiece) {
        for (JoueurCourantListener listener : getJoueurCourantListeners()) {
            listener.pieceChanged(nbPiece);
        }
    }

    /**
     * Notifie que le nombre de nourritures du joueur courant a changé
     * @param nbNourriture Nombre de nourriture
     */
    protected void fireNourritureChanged(int nbNourriture) {
        for (JoueurCourantListener listener : getJoueurCourantListeners()) {
            listener.nourritureChanged(nbNourriture);
        }
    }

    /**
     * Notifie que le nombre d'ouvriers du joueur courant a changé
     * @param nbOuvrier Nombre d'ouvriers
     */
    protected void fireOuvrierChanged(int nbOuvrier) {
        for (JoueurCourantListener listener : getJoueurCourantListeners()) {
            listener.ouvrierChanged(nbOuvrier);
        }
    }

    /**
     * Notifie le changement d'une ressource
     * @param ressource Classe de ressource
     * @param nbRessource Nouvelle quantité
     */
    protected void fireRessourceChanged(Class ressource, int nbRessource) {
        if (ressource == Bois.class) {
            fireBoisChanged(nbRessource);
        } else if (ressource == FerDeLance.class) {
            fireFerDeLanceChanged(nbRessource);
        } else if (ressource == Pierre.class) {
            firePierreChanged(nbRessource);
        } else if (ressource == Poterie.class) {
            firePoterieChanged(nbRessource);
        } else if (ressource == Tissu.class) {
            fireTissuChanged(nbRessource);
        }
    }

    /**
     * Notifie que le nombre de bois du joueur courant a changé
     * @param nbBois Quantité de bois
     */
    protected void fireBoisChanged(int nbBois) {
        for (JoueurCourantListener listener : getJoueurCourantListeners()) {
            listener.boisChanged(nbBois);
        }
    }

    /**
     * Notifie que le nombre de pierres du joueur courant a changé
     * @param nbPierre Quantité de pierre
     */
    protected void firePierreChanged(int nbPierre) {
        for (JoueurCourantListener listener : getJoueurCourantListeners()) {
            listener.pierreChanged(nbPierre);
        }
    }

    /**
     * Notifie que le nombre de poteries du joueur courant a changé
     * @param nbPoterie Quantité de poterie
     */
    protected void firePoterieChanged(int nbPoterie) {
        for (JoueurCourantListener listener : getJoueurCourantListeners()) {
            listener.poterieChanged(nbPoterie);
        }
    }

    /**
     * Notifie que le nombre de tissus du joueur courant a changé
     * @param nbTissu Quantité de tissu
     */
    protected void fireTissuChanged(int nbTissu) {
        for (JoueurCourantListener listener : getJoueurCourantListeners()) {
            listener.tissuChanged(nbTissu);
        }
    }

    /**
     * Notifie que le nombre de fer de lance du joueur courant a changé
     * @param nbFerDeLance Quantité de fer
     */
    protected void fireFerDeLanceChanged(int nbFerDeLance) {
        for (JoueurCourantListener listener : getJoueurCourantListeners()) {
            listener.ferDeLanceChanged(nbFerDeLance);
        }
    }

    /**
     * Notifie que les développements du joueur courant ont changé
     * @param listeDeveloppement Liste des développements
     */
    protected void fireListeDeveloppementChanged(ArrayList<Developpement> listeDeveloppement) {
        for (JoueurCourantListener listener : getJoueurCourantListeners()) {
            listener.listeDeveloppementChanged(listeDeveloppement);
        }
    }

    /**
     * Permet d'ajout un écouteur sur la partie
     * @param listener Ecouteur
     */
    public void addPartieListener(PartieListener listener) {
        listeners.add(PartieListener.class, listener);
    }

    /**
     * Retourne les écouteur de la partie
     * @return Ecouteurs
     */
    public PartieListener[] getPartieListeners() {
        return listeners.getListeners(PartieListener.class);
    }

    /**
     * Notifie que la partie est lancée
     */
    protected void firePartieStarted() {
        for (PartieListener listener : getPartieListeners()) {
            listener.partieStarted();
        }
    }

    /**
     * Notifie que l'étape du jeu a changé
     * @param etapeJeu Etape du jeu
     */
    protected void fireEtapeJeuChanged(EtapeJeu etapeJeu) {
        for (PartieListener listener : getPartieListeners()) {
            listener.etapeCouranteChanged(etapeJeu);
        }
    }

    /**
     * Notifie que le tour a changé
     * @param tour Numéro de tour
     */
    protected void fireTourChanged(int tour) {
        for (PartieListener listener : getPartieListeners()) {
            listener.tourChanged(tour);
        }
    }

    /**
     * Notifie que la liste des joueurs a changé
     * @param listeJoueur Liste des joueurs
     */
    protected void fireListeJoueurChanged(ArrayList<JoueurInterface> listeJoueur) {
        for (PartieListener listener : getPartieListeners()) {
            listener.listeJoueurChanged(listeJoueur);
        }
    }

    /**
     * Notifie que le joueur courant de la partie a changé
     * @param joueurCourant Nouveau joueur courant
     */
    protected void fireJoueurCourantChanged(JoueurInterface joueurCourant) {
        for (PartieListener listener : getPartieListeners()) {
            listener.joueurCourantChanged(joueurCourant);
        }
        Joueur joueur = (Joueur) joueurCourant;
        fireScoreChanged(joueur.getScore());
        fireBateauChanged(joueur.getBateau());
        firePieceChanged(joueur.getPiece());
        fireNourritureChanged(joueur.getNourriture());
        fireOuvrierChanged(joueur.getOuvrier());
        fireBoisChanged(joueur.getBois().getQuantite());
        firePierreChanged(joueur.getPierre().getQuantite());
        firePoterieChanged(joueur.getPoterie().getQuantite());
        fireTissuChanged(joueur.getTissu().getQuantite());
        fireFerDeLanceChanged(joueur.getFer().getQuantite());
        fireListeDeveloppementChanged(joueur.getListeDeveloppements());
        fireDesGardesChanged();
    }

    /**
     * Permet d'ajout un écouteur sur les dés sélectionnés
     * @param listener Ecouteur
     */
    public void addDesListener(DesListener listener) {
        listeners.add(DesListener.class, listener);
    }

    /**
     * Retourne les écouteur des dés
     * @return Liste d'écouteurs
     */
    public DesListener[] getDesListeners() {
        return listeners.getListeners(DesListener.class);
    }

    /**
     * Notifie que les dés sélectionnés ont changés
     */
    protected void fireDesGardesChanged() {
        for (DesListener listener : getDesListeners()) {
            listener.desGardesChanged(partie.getJoueurCourant().getListeDes());
        }
    }

    /**
     * Permet d'ajout un écouteur sur les desastre du joueur
     * @param listener Ecouteur
     */
    public void addDesasteListener(DesastreListener listener) {
        listeners.add(DesastreListener.class, listener);
    }

    /**
     * Retourne les écouteur des desastre du joueur
     * @return Liste découteurs
     */
    public DesastreListener[] getDesastreListeners() {
        return listeners.getListeners(DesastreListener.class);
    }

    /**
     * Notifie un desastre sur le joueur
     * @param desastre Desastre
     */
    protected void fireDesastreSufferedChanged(Desastre desastre) {
        for (DesastreListener listener : getDesastreListeners()) {
            listener.desastreSuffered(desastre);
        }
    }

    /**
     * Notifie la famine sur je joueur
     * @param nbVille Nombre de ville en famine
     */
    protected void fireFamineSufferedChanged(int nbVille) {
        for (DesastreListener listener : getDesastreListeners()) {
            listener.famineSuffered(nbVille);
        }
    }
}
